package IC.AST;

import IC.AST.nodes.*;
import IC.Parser.sym;


public class ASTPrinter implements Visitor {

	ASTNode 	root;
	private int depth;
	private static String SPACES = "                                                     ";
	private static final int ident = 4;
	
	public ASTPrinter(ASTNode symbol) {
		this.root = symbol;
	}

	/**
	 * enter a recursion level - print the header and increment depth
	 * @param header the object title
	 */
	private void enter(String header, ASTNode node) {
		System.out.printf("%2d)",node.getLine(),node.getColumn());
		System.out.println(ASTPrinter.SPACES.substring(0,depth*ident)+""+header);
		depth ++;
	}
	
	/**
	 * exit a recursion level
	 * print closing brcket and derement depth
	 */
	private void leave() {
		depth --;
	//	System.out.println(ASTPrinter.SPACES.substring(0,depth*ident)+" ");
	}

	/**
	 * print non-recursive item
	 * @param header
	 */
	private void enterLeave(String header, ASTNode node) {
		System.out.printf("%2d)",node.getLine(),node.getColumn());
		System.out.println(ASTPrinter.SPACES.substring(0,depth*ident)+"{"+header+"}");
	
	}

	
	public void print() {
		depth = 1;
		root.accept(this);
	}

/*****************************
 * visit functions
 */

	public Object visit(Program that) {
		enter("Program(classes)",that);
		if(that.getClasses() != null && that.getClasses().size() > 0) {
        		for (ClassDecl cls: that.getClasses()) {
        			cls.accept(this);
        		}
		} else {
		    enterLeave("null",that);
		}
		leave();
		return null;
	}
		
	public Object visit(ArrayType that) {
		enter("ArrayType(elem type)",that);
		that.getElemType().accept(this);
		leave();
		return null;
	}

	public Object visit(ClassDecl that) {
		enter("ClassDecl(members) "+that.getClassID()+" extends:"+that.getBaseClassID(),that);
		if(that.getClassMembers() != null &&  that.getClassMembers().size() > 0) {
        		for (ASTNode cmd : that.getClassMembers()) {
        			cmd.accept(this);
        		}
		} else {
		    enterLeave("null",that);
		}
		leave();
		return null;
	}

	public Object visit(ClassType that) {
		enterLeave("ClassType class:"+that.getClassID(),that);
		return null;
	}

	public Object visit(FieldsDecl that) {
		StringBuffer s = new StringBuffer("FieldsDecl(type) ");
		for(String id : that.getIDs()) {
			s.append(id);
			s.append(" ");
		}
		enter(s.toString(),that);
		that.getType().accept(this);
		leave();
		return null;
	}

	public Object visit(FormalDecl that) {
		enter("FormalDecl "+that.getID(),that);
		that.getType().accept(this);
		leave();
		return null;
	}

	public Object visit(MethodDecl that) {
		StringBuffer s = new StringBuffer("MethodDecl(rettype,formals,body) ");
		s.append(that.getMethodName());
		if(that.isStatic()) s.append(" static");
		enter(s.toString(),that);
		
		if(that.getReturnType() != null) {
		    that.getReturnType().accept(this);
		} else { 
		    enterLeave("null",that);
		}
		
		if(that.getFormals() != null && that.getFormals().size() > 0) {
			for(FormalDecl fd : that.getFormals()) {
				fd.accept(this);
			} 
		} else {
		    enterLeave("null", that);
		}

		if(that.getBody() != null) {
			that.getBody().accept(this);
		} else {
		    enterLeave("null", that);
		}
		
		leave();
		return null;
	}

	public Object visit(PrimitiveType that) {
		switch (that.getKind()) {
		case sym.INT: enterLeave("PrimitiveType kind:int",that); break; 
		case sym.BOOLEAN: enterLeave("PrimitiveType kind:boolean",that); break; 
		case sym.STRING: enterLeave("PrimitiveType kind:string",that); break; 
		default:
			enterLeave("PrimitiveType unknown:"+that.getKind(),that); break; 
		}
		return null;
	}

	public Object visit(ArrayElmVar that) {
		enter("ArrayElementVar(array,index)",that);
		that.getArray().accept(this);
		that.getIndex().accept(this);
		leave();
		return null;
	}

	public Object visit(BinaryExpr that) {
		enter("BinaryExpr(left,right) op:"+that.getOp(),that);
		that.getLeft().accept(this);
		that.getRight().accept(this);
		leave();
		return null;
	}

	public Object visit(BoolLiteral that) {
		enterLeave("BoolLiteral value:"+that.getValue(),that);
		return null;
	}


	public Object visit(StaticCall that) {
		enter("StaticCall(args) classID:"+that.getClassID()+" method:"+that.getMethodID(),that);
		if(that.getArgs() != null && that.getArgs().size() > 0) {
        		for ( Expression arg : that.getArgs()) {
        			arg.accept(this);
        		}
		} else {
		    enterLeave("null", that);
		}
		leave();
		return null;
	}


	public Object visit(VirtualCall that) {
		enter("VirtualCall(instance,args) method:"+that.getMethodID(),that);
		if (that.getInstance() != null) {
			that.getInstance().accept(this);
		} else {
		    enterLeave("null", that);
		}
		
		if(that.getArgs() != null && that.getArgs().size() > 0) {
        		for ( Expression arg : that.getArgs()) {
        			arg.accept(this);
        		}
		} else {
		    enterLeave("null", that);
		}
		
		leave();

		return null;
	}


	public Object visit(IntLiteral that) {
		enterLeave("IntLiteral value:"+that.getValue(),that);
		return null;
	}


	public Object visit(LengthExpr that) {
		enter("LengthExpr(array)",that);
		that.getArray().accept(this);
		leave();
		return null;
	}


	public Object visit(SimpleVar that) {
		enterLeave("SimpleVar id:"+that.getID(),that);
		return null;
	}


	public Object visit(MemberVar that) {
		enter("MemberVar(instance) id:"+that.getID(),that);
		that.getInstance().accept(this);
		leave();
		return null;
	}


	public Object visit(NewArrayExpr that) {
		enter("NewArrayExpr(type,size)",that);
		that.getElemType().accept(this);
		that.getSize().accept(this);
		leave();
		return null;
	}

	
	public Object visit(NewClassExpr that) {
		enterLeave("NewClassExpr clsid:"+that.getClassID(),that);
		return null;
	}

	
	public Object visit(NullLiteral that) {
		enterLeave("NullLiteral",that);
		return null;
	}


	public Object visit(StringLiteral that) {
		enterLeave("StringLiteral value:\""+that.getValue()+"\"",that);
		return null;
	}


	public Object visit(ThisExpr that) {
		enterLeave("ThisExpr",that);
		return null;
	}


	public Object visit(UnaryExpr that) {
		enter("UnaryExpr(op,operand)"+that.getOp(),that);
		that.getOperand().accept(this);
		leave();		
		return null;
	}


	public Object visit(AssignStmt that) {
		enter("AssignStmt(src,dest)",that);
		that.getSrc().accept(this);
		that.getDest().accept(this);
		leave();
		return null;
	}


	public Object visit(StmtBlock that) {
		enter("StmtBlock(statements)",that);
		if(that.getStmts() != null && that.getStmts().size() > 0) {
        		for(Statement stmt : that.getStmts()) {
        			stmt.accept(this);
        		} 
		} else {
		    enterLeave("null", that);
		}
		leave();
		return null;
	}


	public Object visit(BreakStmt that) {
		enterLeave("BreakStmt",that);
		return null;
	}


	public Object visit(CallStmt that) {
		enter("CallStmt(call)",that);
		that.getCall().accept(this);
		leave();
		return null;
	}


	public Object visit(ContinueStmt that) {
		enterLeave("ContinueStmt",that);
		return null;
	}


	public Object visit(IfStmt that) {
		enter("IfStmt(condition,then,else)",that);
		that.getCondition().accept(this);
		that.getIfStmt().accept(this);
		if (that.getElseStmt() != null) {
			that.getElseStmt().accept(this);
		} else {
		    enterLeave("null", that);
		}
		leave();
		return null;
	}


	public Object visit(ReturnStmt that) {
		enter("Return(expr)",that);
		if(that.getExpr() != null) {
			that.getExpr().accept(this);
		} else {
		    enterLeave("null", that);
		}
		leave();
		return null;
	}


	public Object visit(VarDeclStmt that) {
		enter("VarDeclStmt(type,init) id:"+that.getId(),that);
		that.getType().accept(this);
		if (that.getInit() != null) {
			that.getInit().accept(this);
		} else {
		    enterLeave("null", that);
		}
		leave();
		return null;
	}


	public Object visit(WhileStmt that) {
		enter("WhileStmt(cond,body)",that);
		that.getCondition().accept(this);
		that.getBody().accept(this);
		leave();
		return null;
	}

	public Object visit(MethodCallStmt that) {
		enter("MethodCallStmt(call)",that);
		if(that.getCall() != null) {
			that.getCall().accept(this);
		} else {
		    enterLeave("null", that);
		}
		leave();
		return null;
	}
}
